﻿using System;
using System.Collections.Generic;
using System.Linq;

using AMC.SubstitutionCache.Events;
using AMC.SubstitutionCache.Models;
using AMC.SubstitutionCache.Services;
using Orchard;
using Orchard.ContentManagement;
using Orchard.ContentManagement.Drivers;
using Orchard.ContentManagement.Drivers.Coordinators;
using Orchard.ContentManagement.Handlers;
using Orchard.ContentManagement.MetaData;
using Orchard.DisplayManagement.Descriptors;
using Orchard.Environment.Configuration;
using Orchard.FileSystems.VirtualPath;
using Orchard.Logging;
using Orchard.Services;

using ILogger = Orchard.Logging.ILogger;
using NullLogger = Orchard.Logging.NullLogger;

namespace AMC.SubstitutionCache.Rendering
{
    public class SubstitutionDriverRenderer : SubstitutionEvents
    {
        private readonly ISubstitutionDriverSelector _driverSelector;
        private readonly ISubstitutionSettings _settings;
        private readonly ISubstitutionCacheStorageProvider _cache;
        private readonly IClock _clock;

        private readonly string _tenantName;

        public SubstitutionDriverRenderer(
            ShellSettings shellSettings,
            ISubstitutionDriverSelector driverSelector,
            ISubstitutionSettings settings,
            IOrchardServices services,
            ISubstitutionCacheStorageProvider cache,
            IClock clock)

    {
            _driverSelector = driverSelector;
            _settings = settings;
            _cache = cache;
            _clock = clock;

            _tenantName = shellSettings.Name;
            Services = services;
            Logger = NullLogger.Instance;
        }

        public ILogger Logger { get; set; }

        private IOrchardServices Services { get; set; }

        public override void Replacing(SubstitutionReplacingContext context)
        {
            var renderInfoList = _driverSelector.GetRenderingInfoForDrivers(context.SubstitutionContext);

            foreach (var renderInfo in renderInfoList)
            {
                var cacheInfo = renderInfo.Driver.GetCacheInfo(context.SubstitutionContext);

                if (string.IsNullOrWhiteSpace(cacheInfo.CacheKey))
                    continue;

                var cacheItem = _cache.GetCacheItem(cacheInfo.CacheKey);
                if (cacheItem != null)
                {
                    context.ShapeOutput = cacheItem.Output;
                    break;
                }
            }
        }

        public override void Replaced(SubstitutionReplacedContext context)
        {
            var renderInfoList = _driverSelector.GetRenderingInfoForDrivers(context.SubstitutionContext);

            if (string.IsNullOrWhiteSpace(context.ShapeOutput))
                return;

            foreach (var renderInfo in renderInfoList)
            {
                var cacheInfo = renderInfo.Driver.GetCacheInfo(context.SubstitutionContext);

                if (string.IsNullOrWhiteSpace(cacheInfo.CacheKey))
                    continue;

                if (!cacheInfo.Duration.HasValue)
                    cacheInfo.Duration = _settings.DefaultCacheDuration;

                if (cacheInfo.Duration.GetValueOrDefault() <= TimeSpan.Zero) 
                    continue;

                _cache.Set(
                    cacheInfo.CacheKey,
                    new SubstitutionCacheItem
                    {
                        Tenant = _tenantName,
                        CacheKey = cacheInfo.CacheKey,
                        SubstitutionKey = context.SubstitutionContext.Key,
                        Tags = new List<string>(cacheInfo.Tags),
                        CachedOnUtc = _clock.UtcNow,
                        ValidUntilUtc = _clock.UtcNow + cacheInfo.Duration.Value,
                        Output = context.ShapeOutput
                    });
            }
        }

        public override void SubstitutionAdding(SubstitutionAddingContext context)
        {
            if (_driverSelector.GetRenderingInfoForDrivers(context.SubstitutionContext).Any())
                context.ShouldAdd = true;
        }

        public override void ShapeCreating(SubstitutionShapeCreatingContext context)
        {
            foreach (var renderInfo in _driverSelector.GetRenderingInfoForDrivers(context.SubstitutionContext))
            {
                var shape = Render(renderInfo, context.SubstitutionContext);

                if (shape != null)
                {
                    context.Shape = shape;
                    break;
                } 
            }
        }

        protected dynamic Render(SubstitutionDriverRenderingInfo renderInfo, SubstitutionContext context)
        {
            var part = GetPart(context);

            if (part == null)
                return null;

            var contentDisplay = GetContentDisplay(renderInfo.Driver);

            dynamic parent;

            if (renderInfo.UseEditor) 
                parent = contentDisplay.BuildEditor(part, context.GroupId);
            else
                parent = contentDisplay.BuildDisplay(part, context.DisplayType, context.GroupId);

            return ShapeHelper.FindShape(parent, renderInfo, context, Services.WorkContext.Layout);
        }

        protected IContent GetPart(SubstitutionContext context)
        {
            // get content item from context
            IContent part = context.ContentPart;

            if (part == null && context.ContentId > 0)
                part = Services.ContentManager.Get(context.ContentId).Get(context.ContentPartType);

            if (part == null)
            {
                Logger.Debug("Part is null. Can't render. Returning string.Empty.");
                return null;
            }

            return part;
        }

        // this is a hideous hack, but I only want an small subset of the system to run
        // so I can surgically re-render the specified shape
        protected IContentDisplay GetContentDisplay(IContentPartDriver driver)
        {
            var contentDefinitionManager = Services.WorkContext.Resolve<IContentDefinitionManager>();
            var shapeTableLocator = Services.WorkContext.Resolve<Lazy<IShapeTableLocator>>();
            var pathProvider = Services.WorkContext.Resolve<IVirtualPathProvider>();
            var workContextAccessor = Services.WorkContext.Resolve<IWorkContextAccessor>();

            var coordinator = new ContentPartDriverCoordinator(new[] { driver }, contentDefinitionManager);

            var requestContext = Services.WorkContext.HttpContext.Request.RequestContext;

            var contentDisplay =
                new DefaultContentDisplay(
                    new Lazy<IEnumerable<IContentHandler>>(() => new[] { coordinator }),
                    Services.New,
                    shapeTableLocator,
                    requestContext,
                    pathProvider,
                    workContextAccessor);

            return contentDisplay;
        }

        
    }
}